package cn.felord.app.config;

import cn.felord.app.data.Rest;
import cn.felord.app.data.RestBody;
import cn.felord.app.entity.SysUser;
import cn.felord.app.security.*;
import cn.felord.app.security.filter.CustomUsernamePasswordAuthenticationFilter;
import cn.felord.app.security.filter.JwtAuthenticationFilter;
import cn.felord.app.security.filter.RefreshJwtTokenFilter;
import cn.felord.app.service.ISysRoleService;
import cn.felord.app.service.ISysUserService;
import cn.felord.app.util.RequestUtil;
import cn.felord.app.util.ResponseUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.GenericFilterBean;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * The type Multi web security config.
 *
 * @author Dax
 * @since 15 :00  2019-04-08
 */
@Slf4j
@Configuration
@AutoConfigureAfter(JwtConfig.class)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class MultiWebSecurityConfig {
    /**
     * 角色必须携带前缀ROLE_
     **/
    private static final String ROLE_PREFIX = "ROLE_";
    /**
     * APP 用户角色
     */
    private static final String APP_USER_ROLE_NAME = "APP_U";
    /**
     * bearer 前缀 后有空格 共7个字符位
     */
    private static final String TOKEN_BEARER_PREFIX = "Bearer ";


    /**
     * 当用户尝试访问安全的REST资源而不提供任何凭据时，将调用此方法发送401 响应
     */
    @Resource
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Resource
    private JwtTokenGenerator jwtTokenGenerator;
    @Resource
    private JwtTokenStorage jwtTokenStorage;
    @Resource
    private JwtPairBuilder jwtPairBuilder;
    @Resource
    private ISysUserService iSysUserService;
    @Resource
    private ISysRoleService sysRoleService;

    /**
     * 密码机.
     *
     * @return the password encoder
     */
    @Bean
    @Primary
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder(10);
    }


    /**
     * 失败登录处理器 处理登录失败后的逻辑 登录失败返回信息 以此为依据跳转
     *
     * @return the authentication failure handler
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }
            Map<String, Object> map = new HashMap<>(2);

            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("flag", "failure_login");
            ResponseUtil.responseJsonWriter(response, RestBody.failureData(map, "验证失败", "-9999"));
        };
    }

    /**
     * 成功退出登录 处理  默认地址  /logout.
     *
     * @return the logout success handler
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }
            Map<String, Object> map = new HashMap<>(2);
            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("flag", "success_logout");
            ResponseUtil.responseJsonWriter(response, RestBody.okData(map, "退出成功"));
        };
    }

    /**
     * Jwt logout handler logout handler.
     *
     * @return the logout handler
     */
    @Bean
    public LogoutHandler jwtLogoutHandler() {
        return (request, response, authentication) -> {
            String header = request.getHeader("Authorization");

            if (header != null && header.startsWith(TOKEN_BEARER_PREFIX)) {
                String token = header.replace(TOKEN_BEARER_PREFIX, "");
                JwtClaims jwtClaims = jwtTokenGenerator.decodeAndVerify(token);
                if (Objects.nonNull(jwtClaims)) {
                    String userId = jwtClaims.getAud();
                    jwtTokenStorage.expire(userId);
                }
            }
            SecurityContextHolder.getContext().setAuthentication(null);
        };
    }


    /**
     * 自定义拒绝访问处理 返回json.
     *
     * @return the access denied handler
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }

            Map<String, Object> map = new HashMap<>(2);
            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("uri", request.getAttribute("javax.servlet.error.request_uri"));
            ResponseUtil.responseJsonWriter(response, RestBody.build(HttpServletResponse.SC_FORBIDDEN, map, "U are denied", "-9999"));
        };
    }

    /**
     * 成功登录处理器 处理成功登录后的逻辑 登录成功返回认证信息 以此为依据跳转.
     *
     * @return the authentication success handler
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }
            Map<String, Object> map = new HashMap<>(5);
            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("flag", "success_login");
            SecureUser principal = (SecureUser) authentication.getPrincipal();
            String userId = principal.getUserId();
            String username = principal.getUsername();
            Collection<GrantedAuthority> authorities = principal.getAuthorities();
            Set<String> roles = new HashSet<>();
            if (CollectionUtil.isNotEmpty(authorities)) {
                for (GrantedAuthority authority : authorities) {
                    String roleName = authority.getAuthority();
                    roles.add(roleName);
                }
            }


            JwtClaims source = new JwtClaims();

            source.setIss("felord");
            source.setSub("all");
            source.setAud(userId);
            source.setUname(username);
            source.setRoles(roles);


            JwtPair jwtPair = jwtPairBuilder.newJwtPair(source, jwtTokenGenerator, jwtTokenStorage);

            map.put("access_token", jwtPair.getAccessToken());
            map.put("refresh_token", jwtPair.getRefreshToken());
            ResponseUtil.responseJsonWriter(response, RestBody.okData(map, "登录成功"));
        };
    }

    /**
     * wechat  接口安全配置
     */
    @Configuration
    @Order(1)
    public class WeChatWebSecurityConfig extends WebSecurityConfigurerAdapter {
        /**
         * 微信登录入口
         */
        private static final String WECHAT_LOGIN_ENTRY_POINT = "/wx/auth/login-by-weixin";
        /**
         * token 刷新端口
         */
        private static final String REFRESH_TOKEN_ENTRY_POINT = "/wx/auth/token-refresh";
        /**
         * 无密码登录填充值  针对无密码登录场景没有具体意义
         */
        private static final String PASSWORD_TEMP = "A2s8yd6xgGsMr@12nm";


        @Resource
        private PasswordEncoder passwordEncoder;
        @Resource
        private JwtPairBuilder jwtPairBuilder;

        /**
         * We chat refresh jwt token filter refresh jwt token filter.
         *
         * @return the refresh jwt token filter
         */
        @Bean
        public RefreshJwtTokenFilter weChatRefreshJwtTokenFilter() {
            return new RefreshJwtTokenFilter(REFRESH_TOKEN_ENTRY_POINT, jwtTokenGenerator, jwtTokenStorage, jwtPairBuilder);
        }

        /**
         * 微信登录用户信息获取服务
         *
         * @return user details service
         */
        @Bean
        public UserDetailsService weChatUserDetailsService() {
            return username -> {

                try {
                    // 查询对应的用户信息 根据用户信息 获取用户的类型 根据不同的类型走不同的策略  角色注入安全用户

                    QueryWrapper<SysUser> sysUserQueryWrapper = new QueryWrapper<>();

                    sysUserQueryWrapper.lambda().eq(SysUser::getUserName, username).eq(SysUser::getUserState, 1);

                    SysUser sysUser = iSysUserService.getOne(sysUserQueryWrapper);
                    String userId = sysUser.getUserId();

                    Optional<Set<String>> optional = Optional.ofNullable(sysRoleService.queryRolesByUserId(userId));
                    Set<String> roleNames = optional.orElse(new HashSet<>());
                    List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(ArrayUtil.toArray(roleNames, String.class));

                    return new SecureUser(userId, username, sysUser.getPassWord(), authorities);

                } catch (Exception e) {
                    throw new UsernameNotFoundException("user [" + username + "] is not found");
                }

            };
        }


        /**
         * Jwt authentication filter jwt authentication filter.
         *
         * @return the jwt authentication filter
         * @throws Exception the exception
         */
        @Bean
        public JwtAuthenticationFilter weChatJwtAuthenticationFilter() throws Exception {
            return new JwtAuthenticationFilter(super.authenticationManager(), jwtTokenGenerator, jwtTokenStorage);
        }


        /**
         * weChat 登录前置处理器  处理自定义的登录逻辑
         *
         * @return 过滤器 generic filter bean
         */
        @Bean
        public GenericFilterBean weChatLoginPostProcessFilter() {
            return new GenericFilterBean() {
                @Override
                public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

                    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

                    String uri = httpServletRequest.getRequestURI();

                    if (WECHAT_LOGIN_ENTRY_POINT.equals(uri)) {

                        String body = RequestUtil.obtainBody(request);

                        JSONObject jsonObject = JSONUtil.parseObj(body);


//todo  处理自定义逻辑
                        RestBody rest = (RestBody) doLogin(httpServletRequest);

                        if (HttpStatus.OK.value() == rest.getHttpStatus()) {
                            //todo
                            request.setAttribute("username", "实现");
                            request.setAttribute("password", PASSWORD_TEMP);
                        }

                    }

                    chain.doFilter(request, response);
                }
            };
        }


        /**
         * 自定义认证器 用来替换原生{@link UsernamePasswordAuthenticationFilter}
         *
         * @return custom username password authentication filter
         * @throws Exception the exception
         */
        @Bean
        public CustomUsernamePasswordAuthenticationFilter weChatCustomUsernamePasswordAuthenticationFilter() throws Exception {

            CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter = new CustomUsernamePasswordAuthenticationFilter();
            AuthenticationManager authenticationManager = super.authenticationManagerBean();

            customUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager);
            customUsernamePasswordAuthenticationFilter.setPostOnly(true);
            customUsernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(WECHAT_LOGIN_ENTRY_POINT, "POST"));
            customUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
            customUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
            return customUsernamePasswordAuthenticationFilter;

        }

        /**
         * 配置认证器构建器
         *
         * @param auth the auth
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) {
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            daoAuthenticationProvider.setUserDetailsService(weChatUserDetailsService());
            //   使用 BCryptPasswordEncoder  不再需要配置盐 配置将抛出异常
            daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
            auth.authenticationProvider(daoAuthenticationProvider);
        }


        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 采用 jwt token  无需 csrf 安全策略
            http.antMatcher("/wx/**").csrf().disable()
                    // 支持跨域
                    .cors()
                    // 统一的访问拒绝处理
                    .and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler())
                    // 基于token 故采用session 无状态策略
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    // 放行 请求
                    .and()
                    .authorizeRequests().antMatchers(HttpMethod.POST, "/wx/auth/reg-captcha", WECHAT_LOGIN_ENTRY_POINT).permitAll()
                    .antMatchers("/", "/error").anonymous()
                    // swagger start
                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs").anonymous()
                    // swagger end
                    // 其他接口都要认证
                    .and()
                    .authorizeRequests().anyRequest().authenticated()
                    .and()
                    .addFilterBefore(weChatJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                    .addFilterBefore(weChatRefreshJwtTokenFilter(), JwtAuthenticationFilter.class)
                    .addFilterBefore(weChatLoginPostProcessFilter(), UsernamePasswordAuthenticationFilter.class)
                    .addFilterAt(weChatCustomUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                    .formLogin().permitAll()
                    .and()
                    .logout().addLogoutHandler(jwtLogoutHandler()).logoutSuccessHandler(logoutSuccessHandler()).clearAuthentication(true).permitAll();


        }


        /**
         * 处理自定义的登录逻辑
         *
         * @param request req
         * @return rest the rest
         */
        private Rest doLogin(HttpServletRequest request) {


            return RestBody.okData(null, "登录成功!");
        }

    }
/******************************************************************************************************* weChat security end*************************************************************************************************/


}
